package com.ylr.base.common.exception.advice;

import com.ylr.base.common.constant.Constant;
import com.ylr.base.common.enums.*;
import com.ylr.base.common.exception.YlrException;
import com.ylr.base.common.po.RequestLogPo;
import com.ylr.base.common.po.user.UserPo;
import com.ylr.base.common.tool.Result;
import com.ylr.base.common.utils.DataUtils;
import com.ylr.base.common.utils.UserUtils;
import com.ylr.redis.common.exception.RedisException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2023-04-19 22:34:40
 * className: ExceptionAdvice 全局异常处理类
 * version: 1.0
 * description:
 */
public abstract class BaseAdvice {

    private static final Logger log = LoggerFactory.getLogger(BaseAdvice.class);

    private final RabbitTemplate rabbitTemplate;

    public BaseAdvice(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    /**
     * 全局异常处理方法
     * @param request http请求对象
     * @param e       异常对象
     * @return 处理结果
     */
    @ExceptionHandler(Exception.class)
    public Result<Object> serviceException(HttpServletRequest request, Exception e) {
        log.error("系统异常 Uri={} Type={} class={} message: {}", request.getRequestURI(), request.getMethod(), e.getClass(), e.getMessage(), e);
        return Result.fail();
    }

    /**
     * 请求接口类型支持异常处理方法
     * @param request http请求对象
     * @param e       异常对象
     * @return 处理结果
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<Object> httpRequestMethodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException e) {
        log.info("请求接口类型支持异常 Uri={}, Type={}, message: {}", request.getRequestURI(), request.getMethod(), e.getMessage(), e);
        return Result.requestFail();
    }

    /**
     * 请求body携带内容为空异常
     * @param request http请求对象
     * @param e       异常对象
     * @return 处理结果
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Result<Object> httpRequestMethodNotSupportedException(HttpServletRequest request, HttpMessageNotReadableException e) {
        log.info("请求body为空异常 Uri={}, Type={}, message: {}", request.getRequestURI(), request.getMethod(), e.getMessage(), e);
        return Result.requestFail("请求body为空，请确认");
    }

    /**
     * 自定义异常处理方法
     * @param request      http请求对象
     * @param ylrException 异常对象
     * @return 处理结果
     */
    @ExceptionHandler(YlrException.class)
    public Result<Object> serviceException(HttpServletRequest request, YlrException ylrException) {
        log.info("自定义运行时异常 Uri={}, Type={}, code={} message: {}", request.getRequestURI(), request.getMethod(), ylrException.getCode(), ylrException.getMessage(), ylrException);
        return Result.getResult(ylrException);
    }

    /**
     * Redis操作异常处理方法
     * @param request        http请求对象
     * @param redisException 异常对象
     * @return 处理结果
     */
    @ExceptionHandler(RedisException.class)
    public Result<Object> serviceException(HttpServletRequest request, RedisException redisException) {
        log.error("redis操作异常 Uri={}, Type={}, message: {}", request.getRequestURI(), request.getMethod(), redisException.getMessage());
        return Result.fail();
    }

    /**
     * 对象参数错误统一处理方法
     * @param request http请求对象
     * @param e       异常对象
     * @return 处理结果
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Object> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
        log.info("Request 参数错误 Uri={}, Type={} params: {}", request.getRequestURI(), request.getMethod(), e.getTarget());

        BindingResult bindingResult = e.getBindingResult();

        Object target = bindingResult.getTarget();
        if (target == null) {
            String message = ResultEnum.PARAM_FAIL.getMsg();
            FieldError fieldError = bindingResult.getFieldError();
            if (fieldError != null) {
                message = fieldError.getDefaultMessage();
            }
            // 不会执行这个返回逻辑
            log.error("目标对象为空。message={}", message);
            return Result.requestFail(message);
        }
        Class<?> targetClass = target.getClass();

        Map<String, String> map = new HashMap<>();
        StringBuilder sb = new StringBuilder();

        Field[] fields = targetClass.getDeclaredFields();
        for (Field field : fields) {
            String fieldName = field.getName();

            // 更新sb
            DataUtils.replaceStringBuilder(sb, fieldName, Constant.SPLIT);

            int start = fieldName.length() + Constant.SPLIT.length();

            // 获取字段上的所有注解
            Annotation[] annotations = field.getAnnotations();
            for (int i = 0; i < annotations.length; i++) {
                Class<? extends Annotation> annotationType = annotations[i].annotationType();

                // 更新sb
                DataUtils.replaceStringBuilder(sb, start, annotationType.getSimpleName());

                map.put(sb.toString(), String.valueOf(i));
            }
        }

        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            // 字段名称
            String fieldName = fieldError.getField();
            // 注解名称
            String code = fieldError.getCode();

            int start = fieldName.length() + Constant.SPLIT.length();

            // 更新sb
            DataUtils.replaceStringBuilder(sb, fieldName, Constant.SPLIT, code);
            String fieldCode = sb.toString();

            // 更新sb
            DataUtils.replaceStringBuilder(sb, start, "check");
            String fieldCheck = sb.toString();

            String message = map.get(fieldName);
            if (message == null) {
                // 还没记录过改字段的消息提示
                map.put(fieldName, fieldError.getDefaultMessage());

                String index = map.get(fieldCode);
                if (StringUtils.isNotEmpty(index)) {
                    // 记录
                    map.put(fieldCheck, index);
                }
                continue;
            }
            message = null;

            String newIndex = map.get(fieldCode);
            String oldIndex = map.get(fieldCheck);
            if (!StringUtils.isAnyEmpty(newIndex, oldIndex)) {
                int nIndex = Integer.parseInt(newIndex);
                int oIndex = Integer.parseInt(oldIndex);
                if (nIndex < oIndex) {
                    // 优先使用排在前面的注解
                    message = fieldError.getDefaultMessage();
                    // 记录
                    map.put(fieldCheck, newIndex);
                }
            }
            if (message != null) {
                map.put(fieldName, message);
            }
        }
        // 清空sb
        sb.delete(0, sb.length());
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String key = entry.getKey();
            if (key.contains(Constant.SPLIT)) {
                continue;
            }
            sb.append(entry.getValue()).append(',');
        }
        // 删除最后一个字符‘,’
        sb.delete(sb.length() - 1, sb.length());
        String message = sb.toString();

        this.saveRequestLog(request, message);

        return Result.requestFail(sb.toString());
    }

    /**
     * 请求url携带的参数错误统一处理方法
     * @param request http请求对象
     * @param e       异常对象
     * @return 处理结果
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<Object> constraintViolationException(HttpServletRequest request, ConstraintViolationException e) {
        log.info("JSR303校验请求url携带的参数发生错误 Uri={}, Type={} message: {}", request.getRequestURI(), request.getMethod(), e.getMessage(), e);

        String message = e.getMessage();
        if (Objects.nonNull(e.getConstraintViolations())) {
            StringBuilder sb = new StringBuilder();

            for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
                sb.append(constraintViolation.getMessage()).append(',');
            }
            // 删除最后一个字符‘,’
            sb.delete(sb.length() - 1, sb.length());
            message = sb.toString();
        }
        return Result.requestFail(message);

    }

    /**
     * ‘@RequestParam要求携带的参数错误统一处理方法
     * @param request http请求对象
     * @param e       异常对象
     * @return 处理结果
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Result<Object> missingServletRequestParameterException(HttpServletRequest request, MissingServletRequestParameterException e) {
        log.info("@RequestParam要求携带的参数发生错误 Uri={}, Type={}", request.getRequestURI(), request.getMethod(), e);
        String message = e.getParameterName() + "参数不能为空";

        this.saveRequestLog(request, message);

        return Result.requestFail(e.getParameterName() + "参数不能为空");
    }

    /**
     * 保存请求日志
     * @param request http请求对象
     * @param message 请求回复消息
     */
    public void saveRequestLog(HttpServletRequest request, String message) {
        // 获取请求用户
        UserPo userPo = UserUtils.getUser();
        Long userId = CommonEnum.DEFAULT_ID.getLongValue();
        String username = StringUtils.EMPTY;
        if (Objects.nonNull(userPo)) {
            userId = userPo.getId();
            username = userPo.getUsername();
        }
        String fingerprint = request.getHeader(Constant.FINGERPRINT);
        String moduleCode = request.getHeader(Constant.MODULE);
        String channelCode = request.getHeader(Constant.CHANNEL);
        String device = request.getHeader(Constant.DEVICE);
        String os = request.getHeader(Constant.OS);
        // 获取请求源的ip地址
        String requestIp = (String) request.getAttribute(Constant.REQUEST_IP);

        Integer moduleValue = Optional.ofNullable(ModuleEnum.getModuleEnum(moduleCode))
                .map(ModuleEnum::getValue)
                .orElse(Constant.NEGATIVE_ONE);
        Integer channelValue = Optional.ofNullable(ChannelEnum.getChannelEnum(channelCode))
                .map(ChannelEnum::getValue)
                .orElse(Constant.NEGATIVE_ONE);

        RequestLogPo requestLogPo = new RequestLogPo();
        requestLogPo.setUserId(userId)
            .setUsername(username)
            .setFingerprint(fingerprint)
            .setRequestIp(requestIp)
            .setModule(moduleValue)
            .setChannel(channelValue)
            .setDevice(device)
            .setOs(os)
            .setUri(request.getRequestURI())
            .setType(request.getMethod())
            .setClassName(StringUtils.EMPTY)
            .setMethod(StringUtils.EMPTY)
            .setRequestId(MDC.get(Constant.TRACE_ID))
            .setCode(ResultEnum.REQUEST_FAIL.getCode())
            .setMessage(message)
            .setStartTime(0L)
            .setEndTime(0L);
        // 发送一条消息到MQ，异步保存操作日志
        rabbitTemplate.convertAndSend(MqEnum.LOG.getExchange(), MqEnum.LOG.getRoutingKey(), requestLogPo, new CorrelationData(UUID.randomUUID().toString()));
    }
}
